package protocol;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;

import server.NodeInfo;

public class ProtocolServer {
	public static final char ENDING_SEP = '@',
							 VALUE_SEP = '$',
							 BLOCK_SEP = '#',
							 MASK = '\\';
	
	public String escapeString(String toEscape)	{
		if(toEscape == null)
			return "";
		
		StringBuilder result = new StringBuilder((int) 1.05 * toEscape.length()); //zarezerwuj troche wiecej miejsca niz trzeba
		
		for(int i = 0; i < toEscape.length(); i++) {
			char c = toEscape.charAt(i);		
			if(c == MASK || c == BLOCK_SEP || c == VALUE_SEP || c == ENDING_SEP)
				result.append('\\');
			
			result.append(c);		
		}
		return result.toString();
	}
	
	//message musi byc zakonczony @
	public String unescapeString(String toUnescape) {
		StringBuilder result = new StringBuilder(toUnescape.length()); 
		
		for(int i = 0; i < toUnescape.length(); i++) {
			char c = toUnescape.charAt(i);
			
			if(c == MASK) { //zakonczenie @ gwarantuje ze nie index nie wyleci poza tablice
				i++;
				c = toUnescape.charAt(i);
			}
			
			result.append(c);
		}
		
		return result.toString();
	}	
	
	public String [] tokenize(String message) {	
		char current = ' ';
		ArrayList<String> tokens =  new ArrayList<String>();
		StringBuilder currentValue = new StringBuilder();	
		boolean escape = false;
		
		for(int i = 0; i < message.length(); i++) {
			current = message.charAt(i);
						
			if(current == VALUE_SEP && !escape) { //splituj po nieescape'owanych znakach $
				tokens.add(currentValue.toString()); 
				currentValue.setLength(0);
			}
			else {
				currentValue.append(current);
			}

			if(current == MASK && escape)
				escape = false;
			else if(current == MASK)
				escape = true;
			else
				escape = false;
		}
		tokens.add(currentValue.toString());
		
		String [] result = new String[tokens.size()];
		return tokens.toArray(result);
	}
	
	public Command parseCommand(String message) throws CommandException
	{
		String [] tokens = tokenize(message);
		Command commandToExecute = null;
		
		if(tokens == null || tokens.length == 0)
			return commandToExecute;
		
		for(String s : tokens){
			s = unescapeString(s);
		}
		
		if(tokens[0].startsWith("CONNECT") && tokens.length >= 3)
		{
			commandToExecute = new Command("CONNECT", new String[]{tokens[1], tokens[2]});
		}
		else if (tokens[0].startsWith("CONFIGURE ADD") && tokens.length >= 7)
		{
			commandToExecute = new Command("CONFIGURE ADD", new String[]{tokens[1], tokens[2], tokens[3], tokens[4], tokens[5], tokens[6]});
			try {
				Integer.parseInt(tokens[3]);	
			} catch(Exception e) { 
				throw new CommandException("Port podany nie jako liczba calkowita");
			}
		}
		else if (tokens[0].startsWith("CONFIGURE LIST NODES"))
		{
			commandToExecute = new Command("CONFIGURE LIST NODES", null);
		}
		else if (tokens[0].startsWith("CONFIGURE REMOVE") && tokens.length >= 2)
		{
			commandToExecute = new Command("CONFIGURE REMOVE", new String[]{tokens[1]});
		}
		else if (tokens[0].startsWith("SQL") && tokens.length >= 2)
		{
			commandToExecute = new Command("SQL", new String[]{tokens[1]});
		}
		else if (tokens[0].startsWith("QUIT"))
		{
			commandToExecute = new Command("QUIT", null);
		}
		else 
		{
			throw new CommandException("Polecenie nie rozpoznane/nie obslugiwane");
		}
		
		return commandToExecute;
	}
	
	public String connect(ConnectResponseType type, String errorInfo) {
		StringBuilder response = new StringBuilder(20);

		switch(type) {
			case OK: 
				response.append("CONNECT OK"); 
				break;
			case PASSWORD_ERROR: 
				response.append("CONNECT INVALID PASSWORD"); 
				break;
			case GENERIC_ERROR: 
				response.append("CONNECT ERROR").append(VALUE_SEP).append(escapeString(errorInfo)); 
				break;
		}
		return response.append("@").toString();
	}
	
	public String config(ConfigResponseType type) {
		StringBuilder response = new StringBuilder(20);
		
		switch(type) {
			case ADD_OK: 
				response.append("CONFIGURE ADD OK"); 
				break;
			case REMOVE_OK: 
				response.append("CONFIGURE REMOVE OK").append(VALUE_SEP);
				break;
		}
		
		return response.append(ENDING_SEP).toString();
	}
	
	public String config(ArrayList<NodeInfo> nodes) {
		StringBuilder response = new StringBuilder(32);		

		for (NodeInfo node : nodes) {		
			response.append(node.getNodeName())
					.append(VALUE_SEP)
					.append(node.getPort())
					.append(VALUE_SEP)
					.append(node.getDbName())
					.append(VALUE_SEP)
					.append(node.getStatus())
					.append(BLOCK_SEP);
		}		
	
		return response.append(ENDING_SEP).toString();
	}
	
	public String configError(ConfigResponseType type, String errorInfo) {
		StringBuilder response = new StringBuilder(20);
		
		switch(type) {
			case SCHEMA_ERROR: 
				response.append("CONFIGURE ADD SCHEMA ERROR").append(VALUE_SEP).append(escapeString(errorInfo)); 
				break;
			case ADD_ERROR: 
				response.append("CONFIGURE ADD ERROR").append(VALUE_SEP).append(escapeString(errorInfo));
				break;						
			case REMOVE_ERROR: 
				response.append("CONFIGURE REMOVE ERROR").append(VALUE_SEP).append(escapeString(errorInfo));
				break;
		}
		
		return response.append(ENDING_SEP).toString();		
	}
	
	public String sql(SqlResult result)
	{
		int initCapacity = 15 + result.getValue().length();
		StringBuffer response = new StringBuffer(initCapacity);
		
		switch(result.getType()) {
			case Query: 
				response.append("SQL SELECT").append(VALUE_SEP).append(result.getValue());
				break;
			case NonQuery:  
				response.append("SQL NONQUERY").append(VALUE_SEP).append(result.getValue());
				break;
		}	
		
		return response.append(ENDING_SEP).toString();
	}
	
	public String sqlError(String errorInfo)
	{
		int initCapacity = 15 + errorInfo.length();
		StringBuffer response = new StringBuffer(initCapacity);
		response.append("SQL ERROR").append(VALUE_SEP).append(errorInfo);
		return response.append(ENDING_SEP).toString();
	}	
	
	public String genericError(String errorInfo) {
		StringBuffer response = new StringBuffer(errorInfo.length() + 20);
		return  response.append("GENERIC ERROR")
						.append(VALUE_SEP)
						.append(errorInfo)
						.append(ENDING_SEP)
						.toString();
	}

	public String formatResultSet(ResultSet resultData) throws SQLException{
	    ResultSetMetaData metadata = resultData.getMetaData();
	    StringBuilder result = new StringBuilder(50);
	    int columnCount = metadata.getColumnCount();
	    
	    //nazwy kolumn
	    for(int i = 1; i <= columnCount; i++){
	    	result.append(escapeString(metadata.getColumnName(i)));
	    
	    	if(i < columnCount){
	    		result.append(VALUE_SEP);
	    	} else {
	    		result.append(BLOCK_SEP);
	    	}
	    }

	    //nazwy typow
	    for(int i = 1; i <= columnCount; i++){
	    	result.append(escapeString(metadata.getColumnTypeName(i)));
	    	int scale = metadata.getScale(i);
	    	int precision = metadata.getPrecision(i);
	    	
	    	if(precision > 0)
	    		result.append("(" + precision);

	    	if(scale > 0)
	    		result.append("," + scale + ")");
	    		    	
	    	if(precision > 0)
	    		result.append(")");
	    	
	    	if(i < columnCount){
	    		result.append(VALUE_SEP);
	    	} else {
	    		result.append(BLOCK_SEP);
	    	}
	    }
	    
	    while (resultData.next()) {	    
	    	for(int i = 1; i <= columnCount; i++){
		    	result.append(escapeString(resultData.getString(i)));
		    	
		    	if(i < columnCount){
		    		result.append(VALUE_SEP);
		    	} else {
		    		result.append(BLOCK_SEP);
		    	}
		    }	    	    	    	
	    }
		
	    return result.toString();
	}

	public String formatNonQuery(int rowsAffected) {
		return "Wiersze zmienione na skutek komendy: " + rowsAffected;
	}
}
